﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Core.Util;

using GPSCrowdSource.DataStructures;

namespace GPSCrowdSource.Algorithms {



  /// <summary>
  /// Provides a utility class that can analyze a route and determine the behaviors.
  /// </summary>
  public class RouteAnalyzer {

    #region Private Members

    #endregion

    #region Constructor
    public RouteAnalyzer() { }
    #endregion

    #region Private Util

    /// <summary>
    /// Calculate the expected value of a distribution assuming equal representation
    /// E[x] = Sum p(x)g(x) where p(x) = 1/N
    /// </summary>
    private double E(double[] X) {
      double d = 0;
      for(int i=0;i<X.Length;i++) d += X[i];
      return d/((double)X.Length);
      }
    
    #endregion

    #region Public Utility

    /// <summary>
    /// Given a stream of GPS values from a single source (1 device) determine
    /// if the traveler is on time.
    /// </summary>
    /// <param name="inSource">The source to analyze against</param>
    public eAnalysisAnswer Analyze(IDataSource inSource) {
      return AnalyzeFully(inSource).Answer;
      }

    /// <summary>
    /// Given a stream of GPS values from a single source (1 device) determine
    /// if the traveler is on time.
    /// </summary>
    /// <param name="inSource">The source to analyze against</param>
    public AnalysisResults AnalyzeFully(IDataSource inSource) {
      
      try {
        
        GeoPosition pos1 = inSource.ReadNext();
        GeoPosition pos2 = inSource.ReadNext();
        float vel = 0;

        if(pos1 == null || pos2 == null) return new  AnalysisResults(eAnalysisAnswer.NoGpsData,null);

        // Orient gps to be in time order going forward in time such that pos1,pos2 is in time order.
        if(pos1.Time > pos2.Time) {
          GeoPosition tmp = pos1;
          pos1 = pos2;
          pos2 = tmp;
          }

        Station theStation;

        // Find a center position
        GeoPosition center = GeoUtils.GetAverage(new GeoPosition[] {pos1,pos2});
        
        // Determine what line the gps position exists on.
        eRTDLine line = KnowlegeBase.GetLine(pos1);
        eRTDLine line2 = KnowlegeBase.GetLine(pos2);

        if(line != line2) return new AnalysisResults(eAnalysisAnswer.Unknown,null);
        
        // Find the correct route from the knowlege base.
        Route r = RTDDatabase.Instance.Query(line);

        if(r == null) return new AnalysisResults(eAnalysisAnswer.NoRouteData,null);

        // Get the current velocity degrees/sec
        vel = (float)(GeoUtils.GetDistance(pos1,pos2)/Math.Abs((pos2.Time-pos1.Time).TotalSeconds));
          
        // Find closest stations
        Station[] stations = r.FindClosest(center);

        // Determine which one is ahead of us.
        if(stations == null || stations.Length == 0) 
          return  new AnalysisResults(eAnalysisAnswer.NoRouteData,null);
        else if(stations.Length == 1)
          theStation = stations[0];
        else {
            
          // Get the direction we are heading.
          Vector3 dir = (pos2-pos1);

          // Get the vector to each station option.
          Vector3 s1dir = (stations[0]-pos2);
          Vector3 s2dir = (stations[1]-pos2);
            
          // Which ever angle is acute is the station we are heading toward (Possibly not perfect but should be good enough)
          theStation = (dir.GetAngle(s1dir) <= Math.PI/2.0f)?stations[0]:stations[1];
          }
        
        // Compute the expected time it takes to get there in seconds
        float te = (float)(GeoUtils.GetDistance(pos2,theStation)/((double)vel));
        
        // Find an arrival time within the expected time (+/- some tuning value)
        Departure depart = theStation.FindClosestDeparture(pos1,pos2);
        if(depart == null) return new AnalysisResults(eAnalysisAnswer.NoRouteData,null);
        
        if(Math.Abs((depart.Time-pos2.DayTime).TotalSeconds) <= (te + GeoConstants.MAX_ONTIME_SECONDS))
          return new AnalysisResults(eAnalysisAnswer.OnTime,theStation,depart);
        
        return new AnalysisResults(eAnalysisAnswer.Late,theStation,depart);
        }
      catch(Exception ex) {
        Log.Error(ex);
        }

      return new AnalysisResults(eAnalysisAnswer.Unknown,null);
      }

    #endregion

    #region Properties
    #endregion

    }

  }
